The motif analysis functions are now split across 5 focused files:
| File | Contents |
|---|---|
R/motifs-data.R |
Shared constants: triad patterns (2 versions), MAN descriptions, theme |
R/motifs.R |
Core: motif_census(), triad_census(),
extract_triads(), get_edge_list() |
R/motifs-extract.R |
extract_motifs() pipeline with print/plot methods |
R/motifs-plot.R |
Visualization helpers (bar, heatmap, network, triad diagrams) |
R/motifs-temporal.R |
extract_motifs_temporal(),
triad_persistence() + methods |
Statistical analysis of network motifs against a null model.
# Create a directed network with clear structure
set.seed(42)
mat <- matrix(0, 8, 8)
rownames(mat) <- colnames(mat) <- paste0("N", 1:8)
# Add structured edges (feed-forward loops, cycles, stars)
mat[1, 2] <- 1; mat[2, 3] <- 1; mat[1, 3] <- 1 # 030T: feed-forward
mat[4, 5] <- 1; mat[5, 6] <- 1; mat[6, 4] <- 1 # 030C: cycle
mat[7, 1] <- 1; mat[7, 2] <- 1; mat[7, 3] <- 1 # out-star from N7
mat[1, 8] <- 1; mat[2, 8] <- 1; mat[3, 8] <- 1 # in-star to N8
mat[4, 7] <- 1; mat[5, 8] <- 1; mat[6, 7] <- 1 # cross-connections
m <- motif_census(mat, directed = TRUE, n_random = 200, seed = 123)
print(m)
## Network Motif Analysis
## Size: 3-node motifs (directed)
## Null model: configuration (n=200)
##
## Significant motifs:
## motif count expected z p
## 111U 8 1.8 4.51 6.4e-06
##
## Over-represented: 1 | Under-represented: 0
plot(m, show_nonsig = TRUE)
plot(m, type = "heatmap", show_nonsig = TRUE)
plot(m, type = "network", show_nonsig = TRUE)
Simple count of the 16 MAN triad types.
tc <- cograph::triad_census(mat)
tc_df <- data.frame(
Type = names(tc),
Count = as.integer(tc),
Description = .get_man_descriptions()[names(tc)]
)
tc_df <- tc_df[tc_df$Count > 0, ]
knitr::kable(tc_df, row.names = FALSE, caption = "Non-zero triad types")
| Type | Count | Description |
|---|---|---|
| 012 | 31 | Single edge |
| 021D | 2 | Out-star |
| 021U | 3 | In-star |
| 021C | 11 | Chain |
| 030T | 8 | Feed-forward |
| 030C | 1 | Cycle |
Extract specific triads with node labels and edge weights.
# Use a weighted matrix
wmat <- matrix(0, 6, 6)
rownames(wmat) <- colnames(wmat) <- c("Plan", "Execute", "Monitor", "Adapt", "Review", "Reflect")
wmat["Plan", "Execute"] <- 15; wmat["Execute", "Monitor"] <- 12
wmat["Monitor", "Adapt"] <- 8; wmat["Adapt", "Plan"] <- 10
wmat["Plan", "Monitor"] <- 6; wmat["Execute", "Adapt"] <- 4
wmat["Review", "Reflect"] <- 9; wmat["Reflect", "Review"] <- 7
wmat["Monitor", "Review"] <- 5; wmat["Review", "Plan"] <- 3
wmat["Adapt", "Review"] <- 6; wmat["Execute", "Review"] <- 2
triads <- extract_triads(wmat, min_total = 0)
knitr::kable(head(triads[order(-triads$total_weight), ], 10),
row.names = FALSE,
caption = "Top 10 triads by total weight")
| A | B | C | type | weight_AB | weight_BA | weight_AC | weight_CA | weight_BC | weight_CB | total_weight |
|---|---|---|---|---|---|---|---|---|---|---|
| Plan | Execute | Monitor | 030T | 15 | 0 | 6 | 0 | 12 | 0 | 33 |
| Plan | Execute | Adapt | 030C | 15 | 0 | 0 | 10 | 4 | 0 | 29 |
| Plan | Monitor | Adapt | 030C | 6 | 0 | 0 | 10 | 8 | 0 | 24 |
| Execute | Monitor | Adapt | 030T | 12 | 0 | 4 | 0 | 8 | 0 | 24 |
| Adapt | Review | Reflect | 111D | 6 | 0 | 0 | 0 | 9 | 7 | 22 |
| Monitor | Review | Reflect | 111D | 5 | 0 | 0 | 0 | 9 | 7 | 21 |
| Plan | Execute | Review | 030C | 15 | 0 | 0 | 3 | 2 | 0 | 20 |
| Plan | Adapt | Review | 030T | 0 | 10 | 0 | 3 | 6 | 0 | 19 |
| Plan | Review | Reflect | 111U | 0 | 3 | 0 | 0 | 9 | 7 | 19 |
| Execute | Monitor | Review | 030T | 12 | 0 | 2 | 0 | 5 | 0 | 19 |
# Only feed-forward loops
ff <- extract_triads(wmat, type = "030T", min_total = 0)
if (nrow(ff) > 0) {
knitr::kable(ff, row.names = FALSE, caption = "Feed-forward loops (030T)")
} else {
cat("No feed-forward loops found\n")
}
| A | B | C | type | weight_AB | weight_BA | weight_AC | weight_CA | weight_BC | weight_CB | total_weight |
|---|---|---|---|---|---|---|---|---|---|---|
| Plan | Execute | Monitor | 030T | 15 | 0 | 6 | 0 | 12 | 0 | 33 |
| Plan | Adapt | Review | 030T | 0 | 10 | 0 | 3 | 6 | 0 | 19 |
| Execute | Monitor | Adapt | 030T | 12 | 0 | 4 | 0 | 8 | 0 | 24 |
| Execute | Monitor | Review | 030T | 12 | 0 | 2 | 0 | 5 | 0 | 19 |
| Execute | Adapt | Review | 030T | 4 | 0 | 2 | 0 | 6 | 0 | 12 |
| Monitor | Adapt | Review | 030T | 8 | 0 | 5 | 0 | 6 | 0 | 19 |
# Triads involving "Plan"
plan_triads <- extract_triads(wmat, involving = "Plan", min_total = 0)
knitr::kable(head(plan_triads, 10), row.names = FALSE,
caption = "Triads involving 'Plan'")
| A | B | C | type | weight_AB | weight_BA | weight_AC | weight_CA | weight_BC | weight_CB | total_weight |
|---|---|---|---|---|---|---|---|---|---|---|
| Plan | Execute | Monitor | 030T | 15 | 0 | 6 | 0 | 12 | 0 | 33 |
| Plan | Execute | Adapt | 030C | 15 | 0 | 0 | 10 | 4 | 0 | 29 |
| Plan | Execute | Review | 030C | 15 | 0 | 0 | 3 | 2 | 0 | 20 |
| Plan | Execute | Reflect | 012 | 15 | 0 | 0 | 0 | 0 | 0 | 15 |
| Plan | Monitor | Adapt | 030C | 6 | 0 | 0 | 10 | 8 | 0 | 24 |
| Plan | Monitor | Review | 030C | 6 | 0 | 0 | 3 | 5 | 0 | 14 |
| Plan | Monitor | Reflect | 012 | 6 | 0 | 0 | 0 | 0 | 0 | 6 |
| Plan | Adapt | Review | 030T | 0 | 10 | 0 | 3 | 6 | 0 | 19 |
| Plan | Adapt | Reflect | 012 | 0 | 10 | 0 | 0 | 0 | 0 | 10 |
| Plan | Review | Reflect | 111U | 0 | 3 | 0 | 0 | 9 | 7 | 19 |
Detailed motif extraction with flexible filtering and optional significance testing.
em <- extract_motifs(wmat, pattern = "all", min_transitions = 0)
print(em)
## Motif Analysis
## Pattern: all | Edge method: any
## Individuals: 1 | States: 6 | Total triads: 20
##
## Type distribution:
##
## 012 030T 030C 111D 111U
## 6 6 4 3 1
##
## Top 20 triads:
## triad type observed
## 1 Adapt - Review - Reflect 111D 1
## 2 Execute - Adapt - Reflect 012 1
## 3 Execute - Adapt - Review 030T 1
## 4 Execute - Monitor - Adapt 030T 1
## 5 Execute - Monitor - Reflect 012 1
## 6 Execute - Monitor - Review 030T 1
## 7 Execute - Review - Reflect 111D 1
## 8 Monitor - Adapt - Reflect 012 1
## 9 Monitor - Adapt - Review 030T 1
## 10 Monitor - Review - Reflect 111D 1
## 11 Plan - Adapt - Reflect 012 1
## 12 Plan - Adapt - Review 030T 1
## 13 Plan - Execute - Adapt 030C 1
## 14 Plan - Execute - Monitor 030T 1
## 15 Plan - Execute - Reflect 012 1
## 16 Plan - Execute - Review 030C 1
## 17 Plan - Monitor - Adapt 030C 1
## 18 Plan - Monitor - Reflect 012 1
## 19 Plan - Monitor - Review 030C 1
## 20 Plan - Review - Reflect 111U 1
plot(em, n = 15, ncol = 5, node_size = 5, label_size = 6, title_size = 8)
plot(em, type = "types")
plot(em, type = "patterns")
if (requireNamespace("tna", quietly = TRUE)) {
library(tna)
Mod <- tna(group_regulation)
em_sig <- extract_motifs(Mod, pattern = "triangle",
significance = TRUE, n_perm = 50,
top = 20, seed = 42)
print(em_sig)
} else {
cat("tna package not available - skipping significance test demo\n")
}
## Motif Analysis
## Pattern: triangle | Edge method: any
## Individuals: 2000 | States: 9 | Total triads: 20
##
## Type distribution:
##
## 120C 030C 030T 210 120U 120D 300
## 1482 1054 620 581 190 178 79
##
## Top 20 triads:
## triad type observed expected z sig
## 1 cohesion - consensus - emotion 120C 261 104.1 18.89 ***
## 2 consensus - discuss - synthesis 120C 176 54.7 18.29 ***
## 3 consensus - coregulate - discuss 120C 330 177.2 13.83 ***
## 4 adapt - discuss - synthesis 030T 24 5.5 9.20 ***
## 5 adapt - consensus - discuss 120C 88 39.9 8.29 ***
## 6 consensus - emotion - plan 120C 436 339.4 6.66 ***
## 7 consensus - coregulate - emotion 030C 165 113.5 5.17 ***
## 8 consensus - coregulate - plan 120C 301 235.6 5.13 ***
## 9 consensus - coregulate - monitor 030C 73 43.8 4.99 ***
## 10 cohesion - coregulate - emotion 030C 52 28.1 4.77 ***
## 11 coregulate - discuss - monitor 030C 50 27.5 3.95 ***
## 12 cohesion - emotion - plan 030C 122 94.4 3.94 ***
## 13 consensus - monitor - plan 120C 187 149.4 3.33 ***
## 14 consensus - discuss - monitor 120C 127 98.9 3.16 **
## 15 cohesion - consensus - coregulate 030C 79 58.1 3.13 **
## 16 adapt - cohesion - consensus 030C 17 9.8 2.19 *
## 17 adapt - consensus - synthesis 030T 10 5.9 1.75
## 18 coregulate - discuss - synthesis 030C 19 13.6 1.56
## 19 consensus - discuss - emotion 120C 238 219.1 1.47
## 20 discuss - emotion - monitor 030C 39 32.7 1.03
plot(em_sig, n = 20, ncol = 5, node_size = 5, label_size = 6)
plot(em_sig, type = "significance")
Track motif evolution across time windows.
if (requireNamespace("tna", quietly = TRUE)) {
data <- group_regulation
tm <- extract_motifs_temporal(
data,
window_size = 5,
step = 2,
pattern = "all",
min_transitions = 5
)
print(tm)
} else {
cat("tna package not available - skipping temporal demo\n")
}
## Temporal Motif Analysis
## Windows: 6 | Pattern: all
## Window size: 5 | Step: 2
##
## Total occurrences by type:
## type count
## 300 404
## 210 89
## 120C 5
## 120D 4
## 030T 1
## 120U 1
plot(tm, type = "trends", top_n = 8)
plot(tm, type = "heatmap")
Analyze which triads persist, emerge, or fade across time.
pers <- triad_persistence(tm, by = "triad", min_windows = 1)
print(pers)
## Triad Persistence Analysis (by triad)
## Windows: 6 | Triads tracked: 84
##
## Status distribution:
## Persistent: 84 | Transient: 0 | Emerging: 0 | Fading: 0 | Sporadic: 0
##
## Top 15 triads by persistence:
## triad type persistence windows status
## consensus - discuss - emotion 300 1 1,2,3,4,5,6 persistent
## consensus - coregulate - discuss 300 1 1,2,3,4,5,6 persistent
## consensus - emotion - plan 300 1 1,2,3,4,5,6 persistent
## consensus - monitor - plan 300 1 1,2,3,4,5,6 persistent
## cohesion - emotion - plan 300 1 1,2,3,4,5,6 persistent
## cohesion - discuss - emotion 300 1 1,2,3,4,5,6 persistent
## consensus - coregulate - plan 300 1 1,2,3,4,5,6 persistent
## consensus - coregulate - monitor 300 1 1,2,3,4,5,6 persistent
## consensus - emotion - monitor 300 1 1,2,3,4,5,6 persistent
## emotion - monitor - plan 300 1 1,2,3,4,5,6 persistent
## cohesion - consensus - emotion 300 1 1,2,3,4,5,6 persistent
## cohesion - consensus - plan 300 1 1,2,3,4,5,6 persistent
## consensus - coregulate - emotion 300 1 1,2,3,4,5,6 persistent
## coregulate - discuss - emotion 300 1 1,2,3,4,5,6 persistent
## cohesion - consensus - discuss 300 1 1,2,3,4,5,6 persistent
plot(pers, type = "heatmap", top_n = 25)
plot(pers, type = "timeline", top_n = 20)
plot(pers, type = "status")
pers_type <- triad_persistence(tm, by = "type")
print(pers_type)
## Triad Persistence Analysis (by type)
## Windows: 6 | MAN types tracked: 6
##
## Status distribution:
## Persistent: 3 | Transient: 2 | Emerging: 1 | Fading: 0 | Sporadic: 0
##
## Top 6 triads by persistence:
## triad type persistence windows status
## 300 300 1.0000000 1,2,3,4,5,6 persistent
## 210 210 1.0000000 1,2,3,4,5,6 persistent
## 120D 120D 0.6666667 1,3,5,6 persistent
## 120C 120C 0.3333333 5,6 emerging
## 030T 030T 0.1666667 4 transient
## 120U 120U 0.1666667 1 transient
plot(pers_type, type = "heatmap")
plot(pers_type, type = "heatmap", normalize = TRUE)
After refactoring, here are the line counts:
| File | Lines |
|---|---|
| R/motifs-data.R | 127 |
| R/motifs.R | 780 |
| R/motifs-extract.R | 616 |
| R/motifs-plot.R | 435 |
| R/motifs-temporal.R | 1058 |
| Total | 3016 |
Original: 3,220 lines in a single file After refactoring: 3016 lines across 5 files (6% reduction)